﻿namespace kp
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;
    using System.Windows.Forms;
    using StashyLib;

    class Program
    {
        private const char DEFAULT_MASK_CHARACTER = '*';
        private const char SPACE = ' ';

        [STAThread]
        static void Main(string[] args)
        {
            var f = new FileStashy();

            string valueFromPipe = ReadPipeLine();

            if (args.Length == 0)
            {
                // List all keys.
                bool foundOne = false;
                foreach (var s in f.ListKeys<Property>())
                {
                    if (foundOne) Console.WriteLine();
                    Console.Write(s);
                    foundOne = true;
                }
            }

            if (args.Length == 1 && valueFromPipe == null)
            {
                if (args[0].In("h", "?", "help", "/?", "-?", "/h", "-h", "--help"))
                {
                    Console.WriteLine(@"
kp -- a key-password store integrated with the clipboard.
inspired by: https://kv.codeplex.com

usage:

kp hotmail user@hotmail.com
    saves the the username, 'user@hotmail.com' under the key, 'hotmail'
    prompts you for the password

kp hotmail
    show the username 'user@hotmail.com'
    and copy the password to the clipboard

kp
    lists all keys

kp  ho* 
    lists all keys that match the pattern 'ho*'

kp -r hotmail
    will remove the key 'hotmail' (and its username/password) from your store");
                    return;
                }

                // search by key;
                var key = args[0];
                try
                {
                    var property = f.Load<Property>(key);
                    string user = property.Username;
                    //if (key.ToLowerInvariant() == "pass") value = UnprotectString(property.Value);
                    Console.Write("username:" + user);
                    Clipboard.SetDataObject(UnprotectString(property.Password), true);
                }
                catch (FileNotFoundException)
                {
                    bool foundOne = false;
                    if (key.Contains("*") || key.Contains("?"))
                    {
                        // List all keys.
                        foreach (var s in f.ListKeys<Property>(key))
                        {
                            if (foundOne) Console.WriteLine();
                            Console.Write(s);
                            foundOne = true;
                        }
                    }

                    if (!foundOne)
                    {
                        Console.Write("No such key");
                    }
                }
            }
            if (args.Length > 1 || (args.Length == 1 && valueFromPipe != null))
            {
                if (args[0].In("r", "-r", "/r", "--remove"))
                {
                    //To delete a key use -r
                    //, e.g. kp -r a
                    f.Delete<Property>(args[1]);
                    return;
                }

                var key = args[0];
                string data = valueFromPipe;

                if (args.Length > 1)
                {
                    // all of the arguments after the first one are joined up and used as the value.
                    string[] value = new string[args.Length - 1];
                    for (int i = 1; i < args.Length; i++)
                    {
                        value[i - 1] = args[i];
                    }

                    data = string.Join(" ", value);
                }

                try
                {
                    Console.Write("password:");
                    string password = ReadLineMasked();
                    Console.WriteLine();

                    f.Save<Property>(new Property() { Key = key, Username = data, Password = ProtectString(password) }, key);
                    Console.Write("Saved");
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.GetType().ToString());
                }
            }
        }

        /// <summary>
        /// Reads a line from the console, masking the characters.
        /// </summary>
        /// <returns></returns>
        public static string ReadLineMasked()
        {
            return ReadLineMasked(DEFAULT_MASK_CHARACTER);
        }

        /// <summary>
        /// Reads a line from the console, masking the characters.
        /// </summary>
        /// <param name="maskCharacter">The mask character.</param>
        /// <returns></returns>
        public static string ReadLineMasked(char maskCharacter)
        {
            var line = new StringBuilder();
            var info = Console.ReadKey(true);
            while (info.Key != ConsoleKey.Enter)
            {
                if (info.Key != ConsoleKey.Backspace)
                {
                    line.Append(info.KeyChar);
                    Console.Write(maskCharacter);
                }
                else if (line.Length != 0)
                {
                    line.Remove(line.Length - 1, 1);

                    // Remove the extra mask character
                    if (Console.CursorLeft == 0)
                    {
                        Console.CursorTop--;
                        Console.CursorLeft = Console.BufferWidth - 1;
                        Console.Write(SPACE);
                        Console.CursorLeft = Console.BufferWidth - 1;
                        Console.CursorTop--;
                    }
                    else
                    {
                        Console.CursorLeft--;
                        Console.Write(SPACE);
                        Console.CursorLeft--;
                    }
                }

                info = Console.ReadKey(true);
            }

            Console.WriteLine();
            return line.ToString();
        }

        // read the whole pipeline -- or return null if there is nothing in the pipe.
        // hat tip: http://stackoverflow.com/questions/199528/c-console-receive-input-with-pipe/4074212#4074212
        private static string ReadPipeLine()
        {
            string valueFromPipe = null;
            try
            {
                bool isKeyAvailable = System.Console.KeyAvailable;
            }
            catch (InvalidOperationException)
            {
                valueFromPipe = System.Console.In.ReadToEnd();
            }
            return valueFromPipe;
        }

        ////Protect and unprotect string: hat tip: http://stackoverflow.com/questions/269101/c-reading-back-encrypted-passwords/269218#269218
        public static string ProtectString(string value)
        {
            byte[] bytes = Encoding.Unicode.GetBytes(value);
            byte[] protectedValue = ProtectedData.Protect(bytes, null, DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(protectedValue);
        }

        public static string UnprotectString(string protectedValue)
        {
            byte[] bytes = Convert.FromBase64String(protectedValue);
            byte[] value = ProtectedData.Unprotect(bytes, null, DataProtectionScope.CurrentUser);
            return Encoding.Unicode.GetString(value);
        }
    }

    public class Property
    {
        public string Key { get; set; } //e.g site
        public string Username { get; set; }
        public string Password { get; set; }
    }

    //hat tip to http://sysi.codeplex.com
    public static class Extensions
    {
        public static bool In(this string self, params string[] strings)
        {
            foreach (var s in strings)
            {
                if (s.ToLowerInvariant() == self) return true;
            }

            return false;
        }
    }
}
